///
/// Copyright (c) 2021 Erwin Rol <erwin@erwinrol.com>
///
/// SPDX-License-Identifier: Apache-2.0
///

#ifndef ZPP_INCLUDE_ZPP_ERROR_CODE_HPP
#define ZPP_INCLUDE_ZPP_ERROR_CODE_HPP

#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/__assert.h>

namespace zpp {

///
/// @brief enum with zephyr error codes
///
enum class error_code : uint8_t {
  k_ok = 0,
  k_perm = EPERM,                     ///< Not owner
  k_noent = ENOENT,                   ///< No such file or directory
  k_srch = ESRCH,                     ///< No such context
  k_intr = EINTR,                     ///< Interrupted system call
  k_io = EIO,                         ///< I/O error
  k_nxio = ENXIO,                     ///< No such device or address
  k_2big = E2BIG,                     ///< Arg list too long
  k_noexec = ENOEXEC,                 ///< Exec format error
  k_badf = EBADF,                     ///< Bad file number
  k_child = ECHILD,                   ///< No children
  k_again = EAGAIN,                   ///< No more contexts
  k_nomem = ENOMEM,                   ///< Not enough core
  k_acces = EACCES,                   ///< Permission denied
  k_fault = EFAULT,                   ///< Bad address
  k_notblk = ENOTBLK,                 ///< Block device required
  k_busy = EBUSY,                     ///< Mount device busy
  k_exits = EEXIST,                   ///< File exists
  k_xdev = EXDEV,                     ///< Cross-device link
  k_nodev = ENODEV,                   ///< No such device
  k_notdir = ENOTDIR,                 ///< Not a directory
  k_isdir = EISDIR,                   ///< Is a directory
  k_inval = EINVAL,                   ///< Invalid argument
  k_nfile = ENFILE,                   ///< File table overflow
  k_mfile = EMFILE,                   ///< Too many open files
  k_notty = ENOTTY,                   ///< Not a typewriter
  k_txtbsy = ETXTBSY,                 ///< Text file busy
  k_fbig = EFBIG,                     ///< File too large
  k_nospc = ENOSPC,                   ///< No space left on device
  k_spipe = ESPIPE,                   ///< Illegal seek
  k_rofs = EROFS,                     ///< Read-only file system
  k_mlink = EMLINK,                   ///< Too many links
  k_pipe = EPIPE,                     ///< Broken pipe
  k_dom = EDOM,                       ///< Argument too large
  k_range = ERANGE,                   ///< Result too large
  k_nomsg = ENOMSG,                   ///< Unexpected message type
  k_deadlk = EDEADLK,                 ///< Resource deadlock avoided
  k_nolck = ENOLCK,                   ///< No locks available
  k_nostr = ENOSTR,                   ///< STREAMS device required
  k_nodata = ENODATA,                 ///< Missing expected message data
  k_time = ETIME,                     ///< STREAMS timeout occurred
  k_nosr = ENOSR,                     ///< Insufficient memory
  k_proto = EPROTO,                   ///< Generic STREAMS error
  k_badmsg = EBADMSG,                 ///< Invalid STREAMS message
  k_nosys = ENOSYS,                   ///< Function not implemented
  k_notempty = ENOTEMPTY,             ///< Directory not empty
  k_nametoolong = ENAMETOOLONG,       ///< File name too long
  k_loop = ELOOP,                     ///< Too many levels of symbolic links
  k_opnotsupp = EOPNOTSUPP,           ///< Operation not supported on socket
  k_pfnosupport = EPFNOSUPPORT,       ///< Protocol family not supported
  k_connreset = ECONNRESET,           ///< Connection reset by peer
  k_nobufs = ENOBUFS,                 ///< No buffer space available
  k_afnosupport = EAFNOSUPPORT,       ///< Addr family not supported
  k_prototype = EPROTOTYPE,           ///< Protocol wrong type for socket
  k_notsock = ENOTSOCK,               ///< Socket operation on non-socket
  k_noprotoopt = ENOPROTOOPT,         ///< Protocol not available
  k_shutdown = ESHUTDOWN,             ///< Can’t send after socket shutdown
  k_connrefused = ECONNREFUSED,       ///< Connection refused
  k_addrinuse = EADDRINUSE,           ///< Address already in use
  k_connaborted = ECONNABORTED,       ///< Software caused connection abort
  k_netunreach = ENETUNREACH,         ///< Network is unreachable
  k_netdown = ENETDOWN,               ///< Network is down
  k_timeout = ETIMEDOUT,              ///< Connection timed out
  k_hostdown = EHOSTDOWN,             ///< Host is down
  k_hostunreach = EHOSTUNREACH,       ///< No route to host
  k_inprogress = EINPROGRESS,         ///< Operation now in progress
  k_already = EALREADY,               ///< Operation already in progress
  k_destaddrreq = EDESTADDRREQ,       ///< Destination address required
  k_msgsize = EMSGSIZE,               ///< Message size
  k_protonosupport = EPROTONOSUPPORT, ///< Protocol not supported
  k_socktnosupport = ESOCKTNOSUPPORT, ///< Socket type not supported
  k_addrnotavail = EADDRNOTAVAIL,     ///< Can’t assign requested address
  k_netreset = ENETRESET,             ///< Network dropped connection on reset
  k_isconn = EISCONN,                 ///< Socket is already connected
  k_notconn = ENOTCONN,               ///< Socket is not connected
  k_toomanyrefs = ETOOMANYREFS,       ///< Too many references: can’t splice
  k_notsup = ENOTSUP,                 ///< Unsupported value
  k_ilseq = EILSEQ,                   ///< Illegal byte sequence
  k_overflow = EOVERFLOW,             ///< Value overflow
  k_canceled = ECANCELED,             ///< Operation canceled
  k_wouldblock = EWOULDBLOCK,         ///< Operation would block
};

#define ZPP_CASE_ERROR_CODE_STR(ERR)                                           \
  case ERR:                                                                    \
    return #ERR;

constexpr bool is_error_free(error_code v) noexcept {
  return v == error_code::k_ok;
}

inline const char *error_str(error_code err) {
  const auto code = static_cast<uint8_t>(err);
  switch (code) {
  case 0:
    return "OK";
    ZPP_CASE_ERROR_CODE_STR(EPERM);
    ZPP_CASE_ERROR_CODE_STR(ENOENT);
    ZPP_CASE_ERROR_CODE_STR(ESRCH);
    ZPP_CASE_ERROR_CODE_STR(EINTR);
    ZPP_CASE_ERROR_CODE_STR(EIO);
    ZPP_CASE_ERROR_CODE_STR(ENXIO);
    ZPP_CASE_ERROR_CODE_STR(E2BIG);
    ZPP_CASE_ERROR_CODE_STR(ENOEXEC);
    ZPP_CASE_ERROR_CODE_STR(EBADF);
    ZPP_CASE_ERROR_CODE_STR(ECHILD);
    ZPP_CASE_ERROR_CODE_STR(EAGAIN);
    ZPP_CASE_ERROR_CODE_STR(ENOMEM);
    ZPP_CASE_ERROR_CODE_STR(EACCES);
    ZPP_CASE_ERROR_CODE_STR(EFAULT);
    ZPP_CASE_ERROR_CODE_STR(ENOTBLK);
    ZPP_CASE_ERROR_CODE_STR(EBUSY);
    ZPP_CASE_ERROR_CODE_STR(EEXIST);
    ZPP_CASE_ERROR_CODE_STR(EXDEV);
    ZPP_CASE_ERROR_CODE_STR(ENODEV);
    ZPP_CASE_ERROR_CODE_STR(ENOTDIR);
    ZPP_CASE_ERROR_CODE_STR(EISDIR);
    ZPP_CASE_ERROR_CODE_STR(EINVAL);
    ZPP_CASE_ERROR_CODE_STR(ENFILE);
    ZPP_CASE_ERROR_CODE_STR(EMFILE);
    ZPP_CASE_ERROR_CODE_STR(ENOTTY);
    ZPP_CASE_ERROR_CODE_STR(ETXTBSY);
    ZPP_CASE_ERROR_CODE_STR(EFBIG);
    ZPP_CASE_ERROR_CODE_STR(ENOSPC);
    ZPP_CASE_ERROR_CODE_STR(ESPIPE);
    ZPP_CASE_ERROR_CODE_STR(EROFS);
    ZPP_CASE_ERROR_CODE_STR(EMLINK);
    ZPP_CASE_ERROR_CODE_STR(EPIPE);
    ZPP_CASE_ERROR_CODE_STR(EDOM);
    ZPP_CASE_ERROR_CODE_STR(ERANGE);
    ZPP_CASE_ERROR_CODE_STR(ENOMSG);
    ZPP_CASE_ERROR_CODE_STR(EDEADLK);
    ZPP_CASE_ERROR_CODE_STR(ENOLCK);
    ZPP_CASE_ERROR_CODE_STR(ENOSTR);
    ZPP_CASE_ERROR_CODE_STR(ENODATA);
    ZPP_CASE_ERROR_CODE_STR(ETIME);
    ZPP_CASE_ERROR_CODE_STR(ENOSR);
    ZPP_CASE_ERROR_CODE_STR(EPROTO);
    ZPP_CASE_ERROR_CODE_STR(EBADMSG);
    ZPP_CASE_ERROR_CODE_STR(ENOSYS);
    ZPP_CASE_ERROR_CODE_STR(ENOTEMPTY);
    ZPP_CASE_ERROR_CODE_STR(ENAMETOOLONG);
    ZPP_CASE_ERROR_CODE_STR(ELOOP);
    // ZPP_CASE_ERROR_CODE_STR(EOPNOTSUPP);
    ZPP_CASE_ERROR_CODE_STR(EPFNOSUPPORT);
    ZPP_CASE_ERROR_CODE_STR(ECONNRESET);
    ZPP_CASE_ERROR_CODE_STR(ENOBUFS);
    ZPP_CASE_ERROR_CODE_STR(EAFNOSUPPORT);
    ZPP_CASE_ERROR_CODE_STR(EPROTOTYPE);
    ZPP_CASE_ERROR_CODE_STR(ENOTSOCK);
    ZPP_CASE_ERROR_CODE_STR(ENOPROTOOPT);
    ZPP_CASE_ERROR_CODE_STR(ESHUTDOWN);
    ZPP_CASE_ERROR_CODE_STR(ECONNREFUSED);
    ZPP_CASE_ERROR_CODE_STR(EADDRINUSE);
    ZPP_CASE_ERROR_CODE_STR(ECONNABORTED);
    ZPP_CASE_ERROR_CODE_STR(ENETUNREACH);
    ZPP_CASE_ERROR_CODE_STR(ENETDOWN);
    ZPP_CASE_ERROR_CODE_STR(ETIMEDOUT);
    ZPP_CASE_ERROR_CODE_STR(EHOSTDOWN);
    ZPP_CASE_ERROR_CODE_STR(EHOSTUNREACH);
    ZPP_CASE_ERROR_CODE_STR(EINPROGRESS);
    ZPP_CASE_ERROR_CODE_STR(EALREADY);
    ZPP_CASE_ERROR_CODE_STR(EDESTADDRREQ);
    ZPP_CASE_ERROR_CODE_STR(EMSGSIZE);
    ZPP_CASE_ERROR_CODE_STR(EPROTONOSUPPORT);
    ZPP_CASE_ERROR_CODE_STR(ESOCKTNOSUPPORT);
    ZPP_CASE_ERROR_CODE_STR(EADDRNOTAVAIL);
    ZPP_CASE_ERROR_CODE_STR(ENETRESET);
    ZPP_CASE_ERROR_CODE_STR(EISCONN);
    ZPP_CASE_ERROR_CODE_STR(ENOTCONN);
    ZPP_CASE_ERROR_CODE_STR(ETOOMANYREFS);
    ZPP_CASE_ERROR_CODE_STR(ENOTSUP);
    ZPP_CASE_ERROR_CODE_STR(EILSEQ);
    ZPP_CASE_ERROR_CODE_STR(EOVERFLOW);
    ZPP_CASE_ERROR_CODE_STR(ECANCELED);
  // ZPP_CASE_ERROR_CODE_STR(EWOULDBLOCK);
  default:
    return "Unknown error";
  }
}

constexpr bool operator*(error_code v) { return is_error_free(v); }

constexpr error_code to_error_code(int v) noexcept {
  if (v < 0) {
    v = -v;
  }
  return static_cast<error_code>(v);
}

static_assert(error_code::k_perm == to_error_code(EPERM));
static_assert(error_code::k_wouldblock == to_error_code(EWOULDBLOCK));

#define ZPP_ERROR_TRY(error)                                                   \
  do {                                                                         \
    if (not error) {                                                           \
      return error;                                                            \
    }                                                                          \
  } while (0);
} // namespace zpp

#endif // ZPP_INCLUDE_ZPP_ERROR_CODE_HPP
